/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  [DebuggerDisplay( "Count = {Count}" )]
  internal class ListDataItemCollection : IList<RawDataItem>, ICollection<RawDataItem>, INotifyCollectionChanged
  {
    internal ListDataItemCollection( DataSourceCollection dataSource )
    {
      if( dataSource == null )
        throw new ArgumentNullException( "dataSource" );

      m_sourceCollection = dataSource;
      m_sourceCollection.CollectionChanged += new NotifyCollectionChangedEventHandler( this.OnSourceCollectionChanged );
      m_dataSource = new List<ListDataItem>( m_sourceCollection.Count );
      m_readOnlyDataSource = new ReadOnlyCollection<ListDataItem>( m_dataSource );

      // Wraps the data objects.
      this.WrapNewUserObject();
      this.UpdatePrimaryKeys();
    }


    #region Count Property

    public int Count
    {
      get
      {
        return m_dataSource.Count;
      }
    }

    #endregion

    #region Item Property

    public RawDataItem this[ int index ]
    {
      get
      {
        if( index < 0 || index >= m_dataSource.Count )
          throw new ArgumentOutOfRangeException( "index", "index must be greater than or equal to zero and less than the count of items in the data source." );

        return m_dataSource[ index ];
      }
      set
      {
        throw new InvalidOperationException( "An attempt was made to add/insert an element into a read-only collection." );
      }
    }

    #endregion

    #region ElementType Property

    internal Type ElementType
    {
      get
      {
        return m_sourceCollection.ElementType;
      }
    }

    #endregion

    #region PrimaryKeyInfo Property

    internal ListDataItemPrimaryKeyDescription PrimaryKeyInfo
    {
      get
      {
        if( !m_primaryKeyInfo.HasValue )
        {
          m_primaryKeyInfo = new ListDataItemPrimaryKeyDescription(
                               new SortDescription( ListDataItem.PrimaryKeyName, ListSortDirection.Ascending ),
                               typeof( FakePrimaryKey ) );
        }

        return m_primaryKeyInfo.Value;
      }
    }

    private Nullable<ListDataItemPrimaryKeyDescription> m_primaryKeyInfo; //null

    #endregion


    #region CollectionChanged Event

    public event NotifyCollectionChangedEventHandler CollectionChanged;

    private void OnCollectionChanged( NotifyCollectionChangedEventArgs e )
    {
      if( this.CollectionChanged != null )
      {
        this.CollectionChanged( this, e );
      }
    }

    #endregion

    #region ItemChanged Event

    public event EventHandler<ItemChangedEventArgs> ItemChanged;

    private void OnItemChanged( ItemChangedEventArgs e )
    {
      if( this.ItemChanged != null )
      {
        this.ItemChanged( this, e );
      }
    }

    #endregion


    public IEnumerator GetEnumerator()
    {
      return m_dataSource.GetEnumerator();
    }

    public int IndexOf( RawDataItem dataItem )
    {
      // Make sure the data item was created by this collection.
      ListDataItem item = dataItem as ListDataItem;
      if( !this.IsCreator( item ) )
        return -1;

      return item.PrimaryKey.Index;
    }

    internal void Reset()
    {
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded;
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved;

      this.SynchronizeWithDataSource( out itemsAdded, out itemsRemoved );

      // Notify of the changes.
      NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs( NotifyCollectionChangedAction.Reset );
      this.OnCollectionChanged( eventArgs );
    }

    internal void Synchronize()
    {
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded;
      IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved;

      this.SynchronizeWithDataSource( out itemsAdded, out itemsRemoved );

      // Notify the listeners of the items removed.
      foreach( KeyValuePair<int, ListDataItem> itemRemoved in itemsRemoved )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Remove,
                                                       itemRemoved.Value,
                                                       itemRemoved.Key );
        this.OnCollectionChanged( eventArgs );
      }

      // Notify the listeners of the items added.
      foreach( KeyValuePair<int, ListDataItem> itemAdded in itemsAdded )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Add,
                                                       itemAdded.Value,
                                                       itemAdded.Key );
        this.OnCollectionChanged( eventArgs );
      }
    }

    private bool IsCreator( ListDataItem dataItem )
    {
      return ( dataItem != null && object.ReferenceEquals( dataItem.Source, this ) );
    }

    private IList<KeyValuePair<int, ListDataItem>> WrapNewUserObject()
    {
      ICollection sourceCollection = m_sourceCollection;
      lock( sourceCollection.SyncRoot )
      {
        // Create a list of data items that is a one to one match with
        // the user's collection.
        if( m_dataSource.Count <= 0 )
        {
          int itemsCount = sourceCollection.Count;
          while( itemsCount > 0 )
          {
            m_dataSource.Add( null );
            itemsCount--;
          }
        }

        // Replace the blanks in the data source with a new data item
        // that wraps the corresponding user object.
        List<KeyValuePair<int, ListDataItem>> newDataItems = new List<KeyValuePair<int, ListDataItem>>();
        int index = 0;

        foreach( object dataObject in sourceCollection )
        {
          ListDataItem dataItem = m_dataSource[ index ];

          // This is a new user object.
          if( dataItem == null )
          {
            FakePrimaryKey primaryKey = new FakePrimaryKey( m_readOnlyDataSource );
            primaryKey.Index = index;

            // Wrap the user object.
            dataItem = new ListDataItem( m_readOnlyDataSource, primaryKey, dataObject );
            m_dataSource[ index ] = dataItem;

            // This data item is a new data item.
            newDataItems.Add( new KeyValuePair<int, ListDataItem>( index, dataItem ) );

            // Listen to the notification event from the data object.
            this.RegisterItem( dataObject );
          }
          // We must keep the index value in sync with the real data item
          // position if we want to be able to retrieve it from the primary
          // key object.
          else
          {
            dataItem.PrimaryKey.Index = index;
          }

          index++;
        }

        return newDataItems;
      }
    }

    private void UpdatePrimaryKeys()
    {
      if( m_dataSource.Count <= 0 )
        return;

      Func<WeakReference, FakePrimaryKey> selectPrimaryKey = ( WeakReference item ) => item.Target as FakePrimaryKey;

      // Order the WeakReference by the FakePrimaryKey it contains.  The garbage collected
      // primary keys are placed at the end of the list to make them easier to remove.
      m_removedPrimaryKeys = m_removedPrimaryKeys.OrderBy(
                              selectPrimaryKey,
                              new FakePrimaryKeyComparer() ).ToList();

      // Remove every primary key collected by the garbage collector.
      while( true )
      {
        int weakReferenceIndex = m_removedPrimaryKeys.Count - 1;

        if( weakReferenceIndex < 0 )
          break;

        if( m_removedPrimaryKeys[ weakReferenceIndex ].IsAlive )
          break;

        m_removedPrimaryKeys.RemoveAt( weakReferenceIndex );
      }

      // Update the internal value of every primary key alive to keep
      // a right primary key comparison.
      int dataSourceIndex = 0;
      int uniqueValue = 0;
      int uniqueValueOffset = 0;
      IEnumerator<WeakReference> removedPrimaryKeys = m_removedPrimaryKeys.GetEnumerator();
      FakePrimaryKey currentRemovedPrimaryKey = null;

      while( true )
      {
        // Get the next removed primary key that must be updated.
        FakePrimaryKey currentPrimaryKey = null;
        while( removedPrimaryKeys != null && currentRemovedPrimaryKey == null )
        {
          if( removedPrimaryKeys.MoveNext() )
          {
            currentRemovedPrimaryKey = removedPrimaryKeys.Current.Target as FakePrimaryKey;
          }
          // There is no removed primary key left.
          else
          {
            removedPrimaryKeys = null;
          }
        }

        // We have updated every primary key.
        if( currentRemovedPrimaryKey == null && dataSourceIndex >= m_dataSource.Count )
          break;

        // The remaining primary keys to update are in the wrapped list.
        if( currentRemovedPrimaryKey == null )
        {
          currentPrimaryKey = m_dataSource[ dataSourceIndex ].PrimaryKey;
          dataSourceIndex++;
        }
        // The remaining primary keys to update are in the remove list.
        else if( dataSourceIndex >= m_dataSource.Count )
        {
          currentPrimaryKey = currentRemovedPrimaryKey;
          currentRemovedPrimaryKey = null;
        }
        else
        {
          // The internal value of both the active and removed primary keys must
          // be unique to avoid collisions.  The removed primary key at index X is
          // updated before the active primary key at the same index to keep the 
          // ordering has close as possible to the original ordering.
          if( uniqueValue >= currentRemovedPrimaryKey.UniqueValue + uniqueValueOffset )
          {
            currentPrimaryKey = currentRemovedPrimaryKey;
            currentRemovedPrimaryKey = null;
          }
          else
          {
            currentPrimaryKey = m_dataSource[ dataSourceIndex ].PrimaryKey;
            dataSourceIndex++;
          }

          // A new primary key added here will slide the remaining removed primary keys.
          if( currentPrimaryKey.IsNew )
          {
            uniqueValueOffset++;
          }
        }

        currentPrimaryKey.UniqueValue = uniqueValue++;
      }
    }

    private void SynchronizeWithDataSource(
      out IEnumerable<KeyValuePair<int, ListDataItem>> itemsAdded,
      out IEnumerable<KeyValuePair<int, ListDataItem>> itemsRemoved )
    {
      ICollection sourceCollection = m_sourceCollection;

      lock( sourceCollection.SyncRoot )
      {
        List<ListDataItem> newDataSource = new List<ListDataItem>( m_dataSource.Count );

        foreach( object dataObject in sourceCollection )
        {
          ListDataItem dataItem = null;

          // Retrieve the index of the first DataItem that contains the user data object
          // and move it into a temporary list.
          int dataSourceItemsCount = m_dataSource.Count;
          for( int i = 0; i < dataSourceItemsCount; i++ )
          {
            if( m_dataSource[ i ].Data == dataObject )
            {
              dataItem = m_dataSource[ i ];

              newDataSource.Add( dataItem );
              m_dataSource.RemoveAt( i );
              break;
            }
          }

          // The data object wasn't found in the DataItem list, so it is a new user data object.
          // We'll insert a blank that will be filled later.
          if( dataItem == null )
          {
            newDataSource.Add( null );
          }
        }

        // The data items that haven't been move in the temporary list have been
        // removed in the original data source.
        itemsRemoved = ( from dataItem in m_dataSource
                         select new KeyValuePair<int, ListDataItem>( dataItem.PrimaryKey.Index, dataItem ) ).ToList();

        // Move from the temporary list into the data items collection.  The temporary list
        // contains the items in the same order as the original data source.  We aren't swapping
        // the list in case an enumerator is referencing the internal list.
        m_dataSource.Clear();
        m_dataSource.AddRange( newDataSource );
        newDataSource.Clear();
      }

      // Cleanup the data items that have been removed.
      foreach( KeyValuePair<int, ListDataItem> item in itemsRemoved )
      {
        ListDataItem dataItem = item.Value;

        // Mark the data item and it's primary key as deleted.
        m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );
        dataItem.Delete();

        // Remove any event handler on the data object.
        this.UnregisterItem( dataItem.Data );
      }

      // Fill in the blanks that were inserted previously.
      itemsAdded = this.WrapNewUserObject();

      // Update the internal key of each data item for consistency.
      this.UpdatePrimaryKeys();
    }

    private void UpdateIndexes()
    {
      if( m_dataSource.Count <= 0 )
        return;

      for( int i = 0; i < m_dataSource.Count; i++ )
      {
        m_dataSource[ i ].PrimaryKey.Index = i;
      }
    }

    private bool TryGetItem( object dataObject, out ListDataItem dataItem )
    {
      dataItem = m_dataSource.FirstOrDefault( ( ListDataItem item ) => ( item.Data == dataObject ) );

      return ( dataItem != null );
    }

    private void RegisterItem( object item )
    {
      INotifyPropertyChanged observableItem = item as INotifyPropertyChanged;
      if( observableItem == null )
        return;

      observableItem.PropertyChanged += new PropertyChangedEventHandler( this.OnDataObjectPropertyChanged );
    }

    private void UnregisterItem( object item )
    {
      INotifyPropertyChanged observableItem = item as INotifyPropertyChanged;
      if( observableItem == null )
        return;

      observableItem.PropertyChanged -= new PropertyChangedEventHandler( this.OnDataObjectPropertyChanged );
    }

    private void ManageItemsAdded( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Add || e.NewItems.Count <= 0 )
        return;

      // Insert some blanks where the data object will be inserted.
      int index = e.NewStartingIndex;
      int count = e.NewItems.Count;

      while( count > 0 )
      {
        m_dataSource.Insert( index, null );
        count--;
      }

      // Wraps the newly added data objects and synchronize the primary keys.
      IList<KeyValuePair<int, ListDataItem>> newDataItems = this.WrapNewUserObject();
      if( newDataItems.Count > 0 )
      {
        this.UpdatePrimaryKeys();

        foreach( KeyValuePair<int, ListDataItem> newDataItem in newDataItems )
        {
          NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                         NotifyCollectionChangedAction.Add,
                                                         newDataItem.Value,
                                                         newDataItem.Key );
          this.OnCollectionChanged( eventArgs );
        }
      }
    }

    private void ManageItemsRemoved( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Remove || e.OldItems.Count <= 0 )
        return;

      // Find out which data items must be removed.
      int index = e.OldStartingIndex;
      int count = e.OldItems.Count;
      IList<KeyValuePair<int, ListDataItem>> oldDataItems = new List<KeyValuePair<int, ListDataItem>>();

      while( count > 0 )
      {
        ListDataItem dataItem = m_dataSource[ index ];

        // Mark the data item and it's primary key as deleted.
        m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );
        dataItem.Delete();

        // Remove any event handler on the data object.
        this.UnregisterItem( dataItem.Data );

        oldDataItems.Add( new KeyValuePair<int, ListDataItem>( index, dataItem ) );

        // Remove the data item from the collection.
        m_dataSource.RemoveAt( index );
        count--;
      }

      // Notify of the changes.
      if( oldDataItems.Count > 0 )
      {
        this.UpdateIndexes();

        foreach( KeyValuePair<int, ListDataItem> oldDataItem in oldDataItems )
        {
          NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                         NotifyCollectionChangedAction.Remove,
                                                         oldDataItem.Value,
                                                         oldDataItem.Key );
          this.OnCollectionChanged( eventArgs );
        }
      }
    }

    private void ManageItemsReplaced( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Replace )
        return;

      // Fill the list of old data items.
      IList<ListDataItem> oldDataItems = new List<ListDataItem>( e.OldItems.Count );
      int startIndex = e.NewStartingIndex;
      int endIndex = startIndex + e.NewItems.Count;

      // Remove the old data items from the data source.
      for( int i = startIndex; i < endIndex; i++ )
      {
        ListDataItem dataItem = m_dataSource[ i ];

        oldDataItems.Add( dataItem );
        this.UnregisterItem( dataItem.Data );

        dataItem.Delete();
        m_removedPrimaryKeys.Add( new WeakReference( dataItem.PrimaryKey ) );

        m_dataSource[ i ] = null;
      }

      // Fill the list of new data items.
      IList<KeyValuePair<int, ListDataItem>> newDataItems = this.WrapNewUserObject();
      if( newDataItems.Count > 0 )
      {
        this.UpdatePrimaryKeys();
      }

      // Notify of the changes.
      IEnumerable<Tuple<int, ListDataItem, ListDataItem>> replacedItems =
        newDataItems.Zip( oldDataItems, ( x, y ) => new Tuple<int, ListDataItem, ListDataItem>( x.Key, x.Value, y ) );
      foreach( Tuple<int, ListDataItem, ListDataItem> item in replacedItems )
      {
        NotifyCollectionChangedEventArgs eventArgs = new NotifyCollectionChangedEventArgs(
                                                       NotifyCollectionChangedAction.Replace,
                                                       item.Item2,
                                                       item.Item3,
                                                       item.Item1 );
        this.OnCollectionChanged( eventArgs );
      }
    }

    private void ManageItemChanged( ItemChangedDescription itemChanged )
    {
      if( itemChanged == null )
        return;

      // Retrieve the corresponding data item.
      ListDataItem dataItem;
      if( !this.TryGetItem( itemChanged.Item, out dataItem ) )
        return;

      // No data item was found.
      if( dataItem == null )
        return;

      // Notify of the changes.
      ItemChangedDescription newItemChanged = itemChanged.CreateWithNewItem( dataItem );
      ItemChangedEventArgs eventArgs = new ItemChangedEventArgs( new ItemChangedDescription[] { newItemChanged } );
      this.OnItemChanged( eventArgs );
    }

    private void ManageItemsReset( NotifyCollectionChangedEventArgs e )
    {
      if( e.Action != NotifyCollectionChangedAction.Reset )
        return;

      this.Reset();
    }

    private void OnSourceCollectionChanged( object sender, NotifyCollectionChangedEventArgs e )
    {
      if( e.Action == NotifyCollectionChangedAction.Add )
      {
        if( e.NewItems.Count > 0 )
        {
          this.ManageItemsAdded( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Remove )
      {
        if( e.OldItems.Count > 0 )
        {
          this.ManageItemsRemoved( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Replace )
      {
        if( ( e.NewItems.Count > 0 ) && ( e.NewItems.Count == e.OldItems.Count ) )
        {
          this.ManageItemsReplaced( e );
        }
      }
      else if( e.Action == NotifyCollectionChangedAction.Reset )
      {
        this.ManageItemsReset( e );
      }
      else
      {
        throw new InvalidOperationException( "The received collection-changed action is not supported." );
      }
    }

    private void OnDataObjectPropertyChanged( object sender, PropertyChangedEventArgs e )
    {
      this.ManageItemChanged( new ItemChangedDescription( sender, e.PropertyName ) );
    }


    #region IList<RawDataItem> Members

    void IList<RawDataItem>.Insert( int index, RawDataItem item )
    {
      throw new NotSupportedException( "Cannot insert an element into a read-only collection." );
    }

    void IList<RawDataItem>.RemoveAt( int index )
    {
      throw new NotSupportedException( "Cannot remove an element from a read-only collection." );
    }

    #endregion

    #region ICollection<RawDataItem> Members

    bool ICollection<RawDataItem>.IsReadOnly
    {
      get
      {
        return true;
      }
    }


    bool ICollection<RawDataItem>.Contains( RawDataItem item )
    {
      ListDataItem dataItem = item as ListDataItem;
      if( dataItem == null )
        return false;

      return m_dataSource.Contains( dataItem );
    }

    void ICollection<RawDataItem>.CopyTo( RawDataItem[] array, int arrayIndex )
    {
      RawDataItem[] sourceArray = m_dataSource.ToArray();

      Array.Copy( sourceArray, 0, array, arrayIndex, sourceArray.Length );
    }

    void ICollection<RawDataItem>.Clear()
    {
      throw new NotSupportedException( "Cannot clear a read-only collection." );
    }

    void ICollection<RawDataItem>.Add( RawDataItem item )
    {
      throw new NotSupportedException( "Cannot add an element to a read-only collection." );
    }

    bool ICollection<RawDataItem>.Remove( RawDataItem item )
    {
      throw new NotSupportedException( "Cannot remove an element from a read-only collection." );
    }

    #endregion

    #region IEnumerable<RawDataItem> Members

    IEnumerator<RawDataItem> IEnumerable<RawDataItem>.GetEnumerator()
    {
      return m_dataSource.Cast<RawDataItem>().GetEnumerator();
    }

    #endregion


    #region Private Fields

    private DataSourceCollection m_sourceCollection;
    private List<ListDataItem> m_dataSource;
    private ReadOnlyCollection<ListDataItem> m_readOnlyDataSource;
    private IList<WeakReference> m_removedPrimaryKeys = new List<WeakReference>();

    #endregion


    #region Private FakePrimaryKeyComparer Nested Type

    private class FakePrimaryKeyComparer : IComparer<FakePrimaryKey>
    {
      internal FakePrimaryKeyComparer()
      {
      }

      int IComparer<FakePrimaryKey>.Compare( FakePrimaryKey x, FakePrimaryKey y )
      {
        if( x == null && y == null )
        {
          return 0;
        }
        else if( x == null )
        {
          return 1;
        }
        else if( y == null )
        {
          return -1;
        }
        else
        {
          return x.CompareTo( y );
        }
      }
    }

    #endregion

    #region Private FakePrimaryKey Nested Type

    [DebuggerDisplay( "UniqueValue = {UniqueValue}, Index = {Index}" )]
    private class FakePrimaryKey : IComparable<FakePrimaryKey>, IComparable
    {
      internal const int NewKey = int.MinValue;
      private const int InvalidIndex = -1;


      internal FakePrimaryKey( ReadOnlyCollection<ListDataItem> dataList )
      {
        if( dataList == null )
          throw new ArgumentNullException( "dataList" );

        this.UniqueValue = FakePrimaryKey.NewKey;
        this.Index = FakePrimaryKey.InvalidIndex;
        this.Source = dataList;
      }


      internal int UniqueValue
      {
        get;
        set;
      }

      internal int Index
      {
        get;
        set;
      }

      internal IList<ListDataItem> Source
      {
        get;
        private set;
      }

      internal bool IsNew
      {
        get
        {
          return ( this.UniqueValue == FakePrimaryKey.NewKey );
        }
      }

      internal bool IsDeleted
      {
        get;
        private set;
      }


      public int CompareTo( FakePrimaryKey key )
      {
        if( key == null || !object.ReferenceEquals( key.Source, this.Source ) )
          throw new DataInternalException( "Cannot compare keys from different sources." );

        return this.UniqueValue.CompareTo( key.UniqueValue );
      }

      public int CompareTo( object key )
      {
        return this.CompareTo( key as FakePrimaryKey );
      }

      internal void Delete()
      {
        this.IsDeleted = true;
        this.Index = FakePrimaryKey.InvalidIndex;
      }
    }

    #endregion

    #region Private ListDataItem Nested Type

    [DebuggerDisplay( "Data = {Data}" )]
    private class ListDataItem : RawDataItem
    {
      // This property name is used to identify a unique item.
      // It contains invalid property name symbols to make sure it
      // won't collid with an existing property name on the user's
      // data object.
      internal const string PrimaryKeyName = "#@@_PRIMARY_KEY_@@#";


      internal ListDataItem(
        ReadOnlyCollection<ListDataItem> dataList,
        FakePrimaryKey key,
        object dataObject )
        : base( dataObject )
      {
        if( dataList == null )
          throw new ArgumentNullException( "dataList" );

        if( key == null )
          throw new ArgumentNullException( "key" );

        this.PrimaryKey = key;
        this.Source = dataList;
      }


      internal FakePrimaryKey PrimaryKey
      {
        get;
        private set;
      }

      internal IList<ListDataItem> Source
      {
        get;
        private set;
      }


      public override int GetHashCode()
      {
        return this.PrimaryKey.GetHashCode();
      }

      public override bool Equals( object obj )
      {
        ListDataItem dataItem = obj as ListDataItem;

        if( object.ReferenceEquals( dataItem, null ) )
          return false;

        return object.Equals( dataItem.PrimaryKey, this.PrimaryKey );
      }

      protected override bool FindDataMemberValue( string propertyName, out object value )
      {
        if( propertyName == ListDataItem.PrimaryKeyName )
        {
          value = this.PrimaryKey;
          return true;
        }

        return base.FindDataMemberValue( propertyName, out value );
      }

      internal override void Delete()
      {
        base.Delete();
        this.PrimaryKey.Delete();
      }
    }

    #endregion
  }
}
